package com.shiguiwu.springmybatis.javabase.thread.lock;

import cn.hutool.core.util.StrUtil;
import com.shiguiwu.springmybatis.javabase.thread.lock.model.LockModel;
import com.shiguiwu.springmybatis.app.mapper.LockMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @description: mysql实现分布式锁
 * @author: stone
 * @date: Created by 2021/9/10 11:09
 * @version: 1.0.0
 * @pakeage: com.shiguiwu.springmybatis.lock
 */
@Component
@Slf4j
public class MysqlLock implements ILock<LockModel>{

    static ThreadLocal<String> requestIds = new ThreadLocal<>();



    @Autowired
    private LockMapper lockMapper;


    public String getRequestId() {
        String requestId = requestIds.get();
        if (StrUtil.isBlank(requestId)) {
            requestId = UUID.randomUUID().toString();
            requestIds.set(requestId);
        }
        log.info("获取到的requestId===> {}", requestId);
        return requestId;

    }

    /**
     *  获取锁
     * @param lockKey 锁可以
     * @param lockTimeout  持有锁的有效时间，防止死锁
     * @param getTimeout 获取锁超时时间，
     * @return
     */
    @Override
    public boolean lock(String lockKey, long lockTimeout, int getTimeout) throws Exception {
        log.info(" lock start =======================> {}",lockKey);
        //从local中获取 请求id
        String requestId = this.getRequestId();

        //获取锁的结果
        boolean lockResult = false;

        //开始时间
        long startTime = System.currentTimeMillis();

        while (true) {
            LockModel lockModel = lockMapper.selectById(lockKey);
            if (Objects.nonNull(lockModel)) {

                //获取锁对象的请求id
                String reqId = lockModel.getRequestId();
                //如果是空，表示改锁未被占有
                if (StrUtil.isBlank(reqId)) {
                    //马上占有它
                    //设置请求id
                    lockModel.setRequestId(requestId);
                    //设置锁次数
                    lockModel.setLockCount(1);
                    //设置超时时间，防止死锁
                    lockModel.setTimeout(System.currentTimeMillis() + lockTimeout);
                    if (lockMapper.updateById(lockModel) == 1) {
                        lockResult = true;
                        break;
                    }

                }
                //如果request_id和表中request_id一样表示锁被当前线程持有者，此时需要加重入锁
                else if (requestId.equals(reqId)) {
                    //可重入锁
                    lockModel.setTimeout(System.currentTimeMillis() + lockTimeout);
                    //设置获取初次
                    lockModel.setLockCount(lockModel.getLockCount() + 1);
                    if (lockMapper.updateById(lockModel) == 1) {
                        lockResult = true;
                        break;
                    }
                }

                //不为空，也不相等，说明是其他线程占有
                else {
                    //锁不是自己的，并且已经超时了，则重置锁，继续重试
                    if (lockModel.getTimeout() < System.currentTimeMillis()) {
                        //未超时，继续重试
                        this.restLock(lockModel);
                    }
                    //如果未超时，休眠100毫秒，继续重试
                    else {
                        if (startTime + getTimeout > System.currentTimeMillis()) {
                            TimeUnit.MILLISECONDS.sleep(100);
                        }
                        else {
                            //防止长时间阻塞
                            break;

                        }
                    }
                }
            }
            //如果是空，就插入一个锁,重新尝试获取锁
            else {
                lockModel = new LockModel();
                //设置锁key
                lockModel.setLockKey(lockKey);
                lockMapper.insert(lockModel);
            }
        }
        log.info(" lock end =======================> {}",lockKey);
        return lockResult;
    }

    /**
     * 释放锁
     * @param lockKey 锁key
     */
    @Override
    public void unlock(String lockKey) {
        LockModel lockModel = lockMapper.selectById(lockKey);
        //获取当前线程的请求id
        String reqId = this.getRequestId();

        //获取锁次数
        int count = 0;
        //当前线程requestId和库中request_id一致 && lock_count>0，表示可以释放锁
        if (Objects.nonNull(lockModel)
                && reqId.equals(lockModel.getRequestId())
                && (count = lockModel.getLockCount()) > 0) {
            if (count == 1) {
                //重置锁
                this.restLock(lockModel);
            }
            //重入锁的问题,锁的次数减一
            else {
                lockModel.setLockCount(lockModel.getLockCount() - 1);
                //更新次数
                lockMapper.updateById(lockModel);
            }
        }
    }

    /**
     * 重置锁
     * @param lockModel 锁对象
     * @return 更新条数
     */
    @Override
    public int restLock(LockModel lockModel) {
        lockModel.setLockCount(0);
        lockModel.setRequestId("");
        lockModel.setTimeout(0L);
        return lockMapper.updateById(lockModel);
    }

}
